<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>贪吃蛇</title>
  <style>
      body {
          background-color: #eee;
      }

      .container {
          text-align: center;
      }

      .top {
          margin: 20px auto;
          width: 640px;
      }

      #score {
          float: left;
      }

      .main {
          position: absolute;
          left: 50%;
          transform: translateX(-50%);
          width: 642px;
          height: 402px;
      }

      #snake {
          border: 1px solid #000;
          width: 640px;
          height: 400px;
          display: inline-block;
          z-index: 99;
          background-color: rgba(0, 0, 0, .1);
      }

      #mask {
          background-color: rgba(0, 0, 0, .5);
          position: absolute;
          left: 0;
          top: 0;
          width: 100%;
          height: 100%;
          z-index: 100;
          display: block;
          color: #fff;
          line-height: 400px;
          text-align: center;
          font-size: 30px;
          cursor: pointer;
      }
  </style>
</head>
<body>
<div class="container">
  <div class="top">
    <span id="score">Score: 0</span>
    <button id="restart">重新开始</button>
    <button id="stop">暂停</button>
    <button id="continue">继续</button>
  </div>
  <div class="main">
    <canvas id="snake" width="640" height="400"></canvas>
    <div id="mask">开始</div>
  </div>
</div>

<script>
  let greedySnake = null
  let score = document.querySelector('#score')
  let restart = document.querySelector('#restart')
  let stop = document.querySelector('#stop')
  let conti = document.querySelector('#continue')
  let mask = document.querySelector('#mask')

  restart.onclick = () => {
    if (!greedySnake.isStart) return
    greedySnake.start()
  }
  stop.onclick = () => {
    if (greedySnake.isStop || !greedySnake.isStart) return
    greedySnake.stop()
  }
  conti.onclick = () => {
    if (!greedySnake.isStop || !greedySnake.isStart) return
    greedySnake.continue()
  }
  mask.onclick = () => {
    if (!greedySnake.isStart) {
      greedySnake.start()
    } else {
      greedySnake.continue()
    }
  }

  // 大小为64 * 40
  class GreedySnake {
    constructor() {
      this.canvas = document.querySelector('#snake')
      this.ctx = this.canvas.getContext('2d')
      this.maxX = 64          // 最大行
      this.maxY = 40          // 最大列
      this.itemWidth = 10     // 每个点的大小
      this.direction = 'right'// up down right left 方向
      this.speed = 150        // ms 速度
      this.isStop = false     // 是否暂停
      this.isOver = false     // 是否结束
      this.isStart = false    // 是否开始
      this.score = 0          // 分数
      this.timer = null       // 移动定时器
      this.j = 1
      this.canChange = true

      this.grid = new Array()

      for (let i = 0; i < this.maxX; i++) {
        for (let j = 0; j < this.maxY; j++) {
          this.grid.push([i, j])
        }
      }

      this.drawGridLine()
      this.getDirection()
    }

    // 开始
    start() {
      if (this.timer) {
        clearTimeout(this.timer)
      }
      if (!this.isStart) {
        this.isStart = true
      }
      this.score = 0
      this.speed = 150
      this.isStop = false
      this.isOver = false
      this.direction = 'right'
      this.createSnake()
      this.createFood()
      this.draw()
      this.move()
      mask.style.display = 'none'
    }

    // 创建蛇主体
    createSnake() {
      this.snake = [
        [4, 25],
        [3, 25],
        [2, 25],
        [1, 25],
        [0, 25]
      ]
    }

    // 移动
    move() {
      if (this.isStop) return

      let [x, y] = this.snake[0]
      switch (this.direction) {
        case 'left':
          x--
          break
        case 'right':
          x++
          break
        case 'up':
          y--
          break
        case 'down':
          y++
          break
      }

      // 如果下一步不是食物的位置
      if (x !== this.food[0] || y !== this.food[1]) {
        this.snake.pop()
      } else {
        this.createFood()
      }

      if (this.over([x, y])) {
        this.isOver = true
        mask.style.display = 'block'
        mask.innerHTML = '结束'
        return
      }
      if (this.completed()) {
        mask.style.display = 'block'
        mask.innerHTML = '恭喜您，游戏通关'
        return
      }

      this.snake.unshift([x, y])

      this.draw()
      this.canChange = true
      this.timer = setTimeout(() => this.move(), this.speed)
    }

    // 暂停游戏
    stop() {
      if (this.isOver) return
      this.isStop = true
      mask.style.display = 'block'
      mask.innerHTML = '暂停'
    }

    // 继续游戏
    continue() {
      if (this.isOver) return
      this.isStop = false
      this.move()
      mask.style.display = 'none'
    }

    getDirection() {
      // 上38 下40 左37 右39 不能往相反的方向走
      document.onkeydown = (e) => {
        // 在贪吃蛇移动的间隔内不能连续改变两次方向
        if (!this.canChange) return
        switch (e.keyCode) {
          case 37:
            if (this.direction !== 'right') {
              this.direction = 'left'
              this.canChange = false
            }
            break
          case 38:
            if (this.direction !== 'down') {
              this.direction = 'up'
              this.canChange = false
            }
            break
          case 39:
            if (this.direction !== 'left') {
              this.direction = 'right'
              this.canChange = false
            }
            break
          case 40:
            if (this.direction !== 'up') {
              this.direction = 'down'
              this.canChange = false
            }
            break
          case 32:
            // 空格暂停与继续
            if (!this.isStop) {
              this.stop()
            } else {
              this.continue()
            }
            break
        }
      }
    }

    createPos() {
      let [x, y] = this.grid[(Math.random() * this.grid.length) | 0]

      for (let i = 0; i < this.snake.length; i++) {
        if (this.snake[i][0] == x && this.snake[i][1] == y) {
          return this.createPos()
        }
      }

      return [x, y]
    }

    // 生成食物
    createFood() {
      this.food = this.createPos()

      // 更新分数
      score.innerHTML = 'Score: ' + this.score++

      if (this.speed > 50) {
        this.speed--
      }
    }

    // 结束
    over([x, y]) {
      if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) {
        return true
      }

      if (this.snake.some(v => v[0] === x && v[1] === y)) {
        return true
      }
    }

    // 完成
    completed() {
      if (this.snake.length === this.maxX * this.maxY) {
        return true
      }
    }

    // 网格线
    drawGridLine() {
      for (let i = 1; i < this.maxY; i++) {
        this.ctx.moveTo(0, i * this.itemWidth)
        this.ctx.lineTo(this.canvas.width, i * this.itemWidth)
      }

      for (let i = 1; i < this.maxX; i++) {
        this.ctx.moveTo(i * this.itemWidth, 0)
        this.ctx.lineTo(i * this.itemWidth, this.canvas.height)
      }
      this.ctx.lineWidth = 1
      this.ctx.strokeStyle = '#ddd'
      this.ctx.stroke()
    }

    // 绘制
    draw() {
      // 清空画布
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

      this.drawGridLine()

      this.ctx.fillStyle = "#000"
      this.ctx.fillRect(
        this.food[0] * this.itemWidth + this.j,
        this.food[1] * this.itemWidth + this.j,
        this.itemWidth - this.j * 2,
        this.itemWidth - +this.j * 2
      )
      this.j ^= 1

      this.ctx.fillStyle = "green"
      this.ctx.fillRect(
        this.snake[0][0] * this.itemWidth + 0.5,
        this.snake[0][1] * this.itemWidth + 0.5,
        this.itemWidth - 1,
        this.itemWidth - 1
      )
      this.ctx.fillStyle = "red"
      for (let i = 1; i < this.snake.length; i++) {
        this.ctx.fillRect(
          this.snake[i][0] * this.itemWidth + 0.5,
          this.snake[i][1] * this.itemWidth + 0.5,
          this.itemWidth - 1,
          this.itemWidth - 1
        )
      }
    }
  }

  greedySnake = new GreedySnake()
</script>
</body>
</html>
